﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Web.Script.Serialization;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace OpenWaves.Web.Controls
{
    [ToolboxData("<{0}:Upload runat=server></{0}:Upload>")]
    [ParseChildren(ChildrenAsProperties = true)]
    [ControlValueProperty("Files")]
    [ValidationProperty("Files")]
    public class Upload : WebControl, INamingContainer, IPostBackDataHandler
    {
        private static readonly object EventFilesChanged = new Object();
        private readonly PlaceHolder placeHolder = new PlaceHolder();

        private string ContainerId
        {
            get { return string.Format(CultureInfo.InvariantCulture, "{0}_{1}", this.ClientID, "container"); }
        }

        private string SelectorId
        {
            get
            {
                return string.Format(CultureInfo.InvariantCulture, "{0}_{1}", this.ClientID, "selector");
            }
        }

        [TemplateContainer(typeof(Upload)), PersistenceMode(PersistenceMode.InnerProperty)]
        public ITemplate SelectorTemplate { get; set; }

        public IEnumerable<UploadedFileInfo> Files
        {
            get
            {
                return (IEnumerable<UploadedFileInfo>) this.ViewState["files"] ?? Enumerable.Empty<UploadedFileInfo>();
            }
            set { this.ViewState["files"] = value; }
        }

        public bool IncludeJQuery
        {
            get { return (bool?)this.ViewState["includeJQuery"] ?? true; }
            set { this.ViewState["includeJQuery"] = value; }
        }

        public bool AutoPostBack
        {
            get { return (bool?)this.ViewState["autoPostBack"] ?? false; }
            set { this.ViewState["autoPostBack"] = value; }
        }

        //TODO : implement this one
        public bool CanRemoveFromQueue
        {
            get { return (bool?)this.ViewState["canRemoveFromQueue"] ?? false; }
            set { this.ViewState["canRemoveFromQueue"] = value; }
        }

        public bool CausesValidation
        {
            get { return (bool?)this.ViewState["causesValication"] ?? false; }
            set { this.ViewState["causesValication"] = value; }
        }

        public string ValidationGroup
        {
            get { return (string)this.ViewState["validationGroup"] ?? string.Empty; }
            set { this.ViewState["validationGroup"] = value; }
        }

        public string QueueId
        {
            get
            {
                var queueId = (string)this.ViewState["queueId"] ?? string.Empty;
                if (this.DesignMode || queueId.IsNullOrWhiteSpace())
                    return string.Empty;

                return GetClientId(queueId);
            }

            set { this.ViewState["queueId"] = value; }
        }

        private string GetClientId(string selectFileId)
        {
            var wc = this.Page.FindControlRecursive(selectFileId);
            return wc == null ? selectFileId : wc.ClientID;
        }

        public int MaxFileSize
        {
            get { return (int?)this.ViewState["maxFileSize"] ?? 100; }
            set { this.ViewState["maxFileSize"] = value; }
        }

        public bool InitializeUpload
        {
            get { return (bool?) this.ViewState["initializeUpload"] ?? true; }
            set { this.ViewState["initializeUpload"] = value; }
        }

        public string InitializationScript
        {
            get { return string.Format(CultureInfo.InvariantCulture, "{0}.initialize();", this.ClientID); }
        }
        
        public UploadRuntimes Runtimes
        {
            get
            {
                return (UploadRuntimes?)this.ViewState["runtimes"] ??
                       UploadRuntimes.Gears | UploadRuntimes.Silverlight | UploadRuntimes.Html5 | UploadRuntimes.Flash;
            }
            set { this.ViewState["runtimes"] = value; }
        }

        protected override void Render(HtmlTextWriter writer)
        {
            writer.AddAttribute(HtmlTextWriterAttribute.Id, this.ContainerId);
            writer.RenderBeginTag(HtmlTextWriterTag.Div);

            writer.AddAttribute(HtmlTextWriterAttribute.Id, this.SelectorId);
            writer.RenderBeginTag(HtmlTextWriterTag.Div);
            if (this.SelectorTemplate == null)
            {
                writer.AddAttribute(HtmlTextWriterAttribute.Href, "javascript:void(0);");
                writer.RenderBeginTag(HtmlTextWriterTag.A);                
                writer.Write("select file");
                writer.RenderEndTag();
            }
            else
            {                
                SelectorTemplate.InstantiateIn(placeHolder);                
                placeHolder.RenderControl(writer);
            }

            writer.RenderEndTag();

            writer.AddAttribute(HtmlTextWriterAttribute.Name, this.UniqueID);
            writer.AddAttribute(HtmlTextWriterAttribute.Id, this.ClientID);
            writer.AddAttribute(HtmlTextWriterAttribute.Type, "hidden");

            if (this.Files.Any())
            {
                var serializer = new JavaScriptSerializer();
                var fileInfos = serializer.Serialize(this.Files);
                writer.AddAttribute(HtmlTextWriterAttribute.Value, fileInfos);
            }

            writer.RenderBeginTag(HtmlTextWriterTag.Input);
            writer.RenderEndTag();
            writer.RenderEndTag();
        }
        
        protected override void OnPreRender(EventArgs e)
        {
            base.OnPreRender(e);

            if(this.IncludeJQuery)
            {
                this.Page.ClientScript.RegisterClientScriptInclude(typeof(WebResources), "jqueryScript",
                                                    WebResources.JQueryScriptUrl.ToString());
            }

            this.Page.ClientScript.RegisterClientScriptResource(typeof (Upload), WebResources.UploadControl);
            this.Page.ClientScript.RegisterClientScriptResource(typeof(Upload), WebResources.PLUploadScript);

            var runtimes = this.Runtimes.ToString();
            var uploadParams = new
            {
                runtimes = runtimes.ToLower(CultureInfo.CurrentCulture),
                container = this.ContainerId,
                browse_button = this.SelectorId,
                max_file_size = string.Format(CultureInfo.InvariantCulture, "{0}mb", this.MaxFileSize),
                flash_swf_url = this.Page.ClientScript.GetWebResourceUrl(typeof(Upload), WebResources.PLUploadFlash),
                silverlight_xap_url = this.Page.ClientScript.GetWebResourceUrl(typeof(Upload), WebResources.PLUploadSilverlight),
                url = this.ResolveUrl(AssemblyResourceVirtualPathProvider.CreateUrl(typeof(UploadHandler)).ToString()),
                queueId = this.QueueId,
                autoPostBack = this.AutoPostBack,
                autoPostBackScript = this.Page.ClientScript.GetPostBackEventReference(this, "files"),
                stateContainer = this.ClientID
            };

            var declarationScript = String.Format(CultureInfo.InvariantCulture,
                                                  "var {0} = new openWaves.UploadControl({1});",
                                                  this.ClientID,
                                                  new JavaScriptSerializer().Serialize(uploadParams));

            this.Page.ClientScript.RegisterClientScriptBlock(typeof (Upload), "declarationScript" + this.UniqueID,
                                                             declarationScript, true);

            if (InitializeUpload)
            {
                var startupScript = string.Format(CultureInfo.InvariantCulture, "$(function() {{{0}}});", this.InitializationScript);
                this.Page.ClientScript.RegisterStartupScript(typeof(Upload), "startupScript" + this.UniqueID,
                                                             startupScript, true);
            }
        }

        public bool LoadPostData(string postDataKey, NameValueCollection postCollection)
        {
            var presentValue = this.Files.ToArray();

            var serializer = new JavaScriptSerializer();
            var postedValue = (serializer.Deserialize<IEnumerable<UploadedFileInfo>>(postCollection[postDataKey]) ??
                              Enumerable.Empty<UploadedFileInfo>()).ToArray();

            var collectionsEqual = presentValue.All(postedValue.Contains) && postedValue.All(presentValue.Contains);

            if ((presentValue.Any() == false && postedValue.Any()) || collectionsEqual == false)
            {
                this.Files = postedValue;
                return true;
            }

            return false;
        }

        public void RaisePostDataChangedEvent()
        {
            if (AutoPostBack && !Page.IsPostBackEventControlRegistered)
            {
                Page.AutoPostBackControl = this;

                if (this.CausesValidation)
                {
                    Page.Validate(ValidationGroup);
                    return;
                }
            }

            OnFilesChanged(EventArgs.Empty);
        }

        public void Clear()
        {
            this.Files = Enumerable.Empty<UploadedFileInfo>();
        }

        protected virtual void OnFilesChanged(EventArgs e)
        {
            var onChangeHandler = (EventHandler)Events[EventFilesChanged];
            if (onChangeHandler != null) onChangeHandler(this, e);
        }

        public event EventHandler FilesChanged
        {
            add { Events.AddHandler(EventFilesChanged, value); }
            remove { Events.RemoveHandler(EventFilesChanged, value); }
        }
    }

    [Serializable]
    public class UploadedFileInfo
    {
        public string FileName { get; set; }
        public string TempFileName { get; set; }
        public long Size { get; set; }

        public Stream Open()
        {
            return new FileStream(this.TempFileName, FileMode.Open, FileAccess.Read);
        }

        public bool Equals(UploadedFileInfo other)
        {
            if (ReferenceEquals(null, other)) return false;
            if (ReferenceEquals(this, other)) return true;
            return Equals(other.TempFileName, TempFileName) && Equals(other.FileName, FileName);
        }

        public override bool Equals(object obj)
        {
            if (ReferenceEquals(null, obj)) return false;
            if (ReferenceEquals(this, obj)) return true;
            if (obj.GetType() != typeof (UploadedFileInfo)) return false;
            return Equals((UploadedFileInfo) obj);
        }

        public override int GetHashCode()
        {
            unchecked
            {
                return ((TempFileName != null ? TempFileName.GetHashCode() : 0)*397) ^ (FileName != null ? FileName.GetHashCode() : 0);
            }
        }
    }

    [Flags]
    public enum UploadRuntimes
    {
        Gears = 1,
        Html4 = 2,
        Html5 = 4,
        Flash = 8,
        Silverlight = 16,
    }
}